Proxy pattern

Proxy pattern

代理模式为另一个对象提供一个代理对象以控制对这个对象的访问,被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。

远程代理

远程代理即是我们希望通过调用一个本地对象,然后其能将每个请求转发到远程对象上进行。换句话说,客户对象意味它调用的是远程服务上的方法,实际上是代理对象假装有客户要调用的方法,真正执行方法的在远程的对象。

Design_pattern_proxy_1

通过Java RMI我们可以简单实现远程调用,而无需处理细节的网络通信。

Design_pattern_proxy_2

使用Java RMI的具体步骤为:

  1. 创建远程接口
  2. 实现远程接口的类Impl(main方法需要注册到RMI中)
  3. 使用Java rmic生成Impl的stud和skeleton,也就是客户和服务的辅助类。
  4. 在远程机器上启动RMI registry(倾听Impl的注册)
  5. 运行主方法(可以是启动类)将Impl注册到RMI服务上。
  6. 客户端调用Naming方法取得stub对象。
  7. 客户调用哦该stub方法,就像stub就是真正的服务对象一样。

Design_pattern_proxy_3

Design_pattern_proxy_4

服务端实现:

1
2
3
4
5
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}

public class NoQuarterState implements State {
transient GumballMachine gumballMachine;

public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}

public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}

public void dispense() {
System.out.println("You need to pay first");
}

public String toString() {
return "waiting for quarter";
}
}

...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public class GumballMachine
extends UnicastRemoteObject implements GumballMachineRemote
{
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;

State state = soldOutState;
int count = 0;
String location;

public GumballMachine(String location, int numberGumballs) throws RemoteException {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);

this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
this.location = location;
}


public void insertQuarter() {
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

public void turnCrank() {
state.turnCrank();
state.dispense();
}

void setState(State state) {
this.state = state;
}

void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}

public void refill(int count) {
this.count = count;
state = noQuarterState;
}

public int getCount() {
return count;
}

public State getState() {
return state;
}

public String getLocation() {
return location;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getHasQuarterState() {
return hasQuarterState;
}

public State getSoldState() {
return soldState;
}

public State getWinnerState() {
return winnerState;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}

在RMI registry中注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;

if (args.length < 2) {
System.out.println("GumballMachine <name> <inventory>");
System.exit(1);
}

try {
count = Integer.parseInt(args[1]);

gumballMachine =
new GumballMachine(args[0], count);
Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
} catch (Exception e) {
e.printStackTrace();
}
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GumballMonitor {
GumballMachineRemote machine;

public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}

public void report() {
try {
System.out.println("Gumball Machine: " + machine.getLocation());
System.out.println("Current inventory: " + machine.getCount() + " gumballs");
System.out.println("Current state: " + machine.getState());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class GumballMonitorTestDrive {

public static void main(String[] args) {
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://seattle.mightygumball.com/gumballmachine"};

GumballMonitor[] monitor = new GumballMonitor[location.length];

for (int i=0;i < location.length; i++) {
try {
GumballMachineRemote machine =
(GumballMachineRemote) Naming.lookup(location[i]);
monitor[i] = new GumballMonitor(machine);
System.out.println(monitor[i]);
} catch (Exception e) {
e.printStackTrace();
}
}

for(int i=0; i < monitor.length; i++) {
monitor[i].report();
}
}
}

虚拟代理

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再有代理将结果转给客户。

Design_pattern_proxy_5

而虚拟代理通常作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求委托给对象。

比如我们需要创建一个CD封面对象,这个封面图片需要从网上下载。在下外完成前,我们最好也显式一些内容,这时我们就可以使用虚拟代理。

Design_pattern_proxy_6

  • ImageProxy首先创建一个ImageIcon,然后开始从网络上加载图像。
  • 在加载过程中,ImageProxy显式”加载中…”
  • 当图像加载完毕,ImageProxy把所有方法掉用委托给真正的ImageIcon。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;

public ImageProxy(URL url) { imageURL = url; }

public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}

public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}

public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("Loading CD cover, please wait...", x+300, y+190);
if (!retrieving) {
retrieving = true;

retrievalThread = new Thread(new Runnable() {
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ImageComponent extends JComponent {
private Icon icon;

public ImageComponent(Icon icon) {
this.icon = icon;
}

public void setIcon(Icon icon) {
this.icon = icon;
}

public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w)/2;
int y = (600 - h)/2;
icon.paintIcon(this, g, x, y);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable cds = new Hashtable();

public static void main (String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}

public ImageProxyTestDrive() throws Exception{
cds.put("Ambient: Music for Airports","http://images.amazon.com/images/P/B000003S2K.01.LZZZZZZZ.jpg");
cds.put("Buddha Bar","http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima","http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma","http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.","http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure","http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2","http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");

URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);

for(Enumeration e = cds.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
}
});
}

// set up frame and menus

Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);

}

URL getCDUrl(String name) {
try {
return new URL((String)cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}

使用Java API代理实现保护代理

Java在java.lang.reflect包中有自己的代理支持,利用哦该这个包我们可以在运行时动态的创建一个代理类,实现一个或多个接口,并将方法的调用转发到我们所指定的类。因为实际的代理是在运行时创建的,所以这个技术称为动态代理。

Design_pattern_proxy_7

我们不需要自己创建Proxy类,也不能往里面添加代码。我们希望处理代码应该放在InvocationHandler中。InvocationHandler的工作是响应代理的任何调用,可以认为是代理收到方法调用后实际执行请求的对象。

假设我们正在实现一个信息管理服务,我们希望当前用户可以更改自己的基本信息,但不是不能更改自己的客观信息,同时其他用户也不能更改当前用户的基本信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public interface PersonBean {

String getName();
String getGender();
String getInterests();
int getHotOrNotRating();

void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);

}

public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;

public String getName() {
return name;
}

public String getGender() {
return gender;
}

public String getInterests() {
return interests;
}

public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating/ratingCount);
}


public void setName(String name) {
this.name = name;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setInterests(String interests) {
this.interests = interests;
}

public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}

这时我们可以为PersonBean创建动态代理,以过滤对这个类的请求:

  1. 创建InvocationHandler(如果有多种模式,需要创建多个,这里我们分为两类,操作用户是用户本人或是其他人,所以需要两个InvocationHandler)。
  2. 写代码创建动态代理。
  3. 利用适当的代理保证PersonBean对象,并将这个代理提供给客户,而不是对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class OwnerInvocationHandler implements InvocationHandler { 
PersonBean person;

public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class NonOwnerInvocationHandler implements InvocationHandler { 
PersonBean person;

public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class MatchMakingTestDrive {
Hashtable datingDB = new Hashtable();

public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}

public MatchMakingTestDrive() {
initializeDatabase();
}

public void drive() {
PersonBean joe = getPersonFromDatabase("Joe Javabean");
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownerProxy.getName());
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());

PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
System.out.println("Name is " + nonOwnerProxy.getName());
try {
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}

PersonBean getOwnerProxy(PersonBean person) {

return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}

// 获得这个对象的代理对象
PersonBean getNonOwnerProxy(PersonBean person) {

return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}

PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}

void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);

PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}